package com.keya.features.gateway;

import com.keya.device.GatewayProperties;
import com.keya.features.job.MonitorDiskJob;
import io.micrometer.core.instrument.Metrics;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.Options;
import org.apache.commons.lang.StringUtils;
import org.dcm4che3.data.Attributes;
import org.dcm4che3.data.Tag;
import org.dcm4che3.data.VR;
import org.dcm4che3.io.DicomOutputStream;
import org.dcm4che3.net.*;
import org.dcm4che3.net.pdu.AAssociateAC;
import org.dcm4che3.net.pdu.AAssociateRJ;
import org.dcm4che3.net.pdu.AAssociateRQ;
import org.dcm4che3.net.pdu.PresentationContext;
import org.dcm4che3.net.service.BasicCEchoSCP;
import org.dcm4che3.net.service.BasicCStoreSCP;
import org.dcm4che3.net.service.DicomServiceException;
import org.dcm4che3.net.service.DicomServiceRegistry;
import org.dcm4che3.tool.common.CLIUtils;
import org.dcm4che3.tool.common.DetectEndOfOptionsPosixParser;
import org.dcm4che3.util.SafeClose;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
public class Gateway {

    @Resource
    GatewayProperties gateWayProperties;
    @Resource
    StudyTransferManager studyTransferManager;
    @Resource
    MonitorDiskJob monitorDiskJob;

    private final Logger LOG = LoggerFactory.getLogger(Gateway.class);

    private final BasicCStoreSCP cstoreSCP = new BasicCStoreSCP("*") {

        @Override
        protected void store(Association as, PresentationContext pc,
                             Attributes rq, PDVInputStream data, Attributes rsp)
                throws IOException {
            long from = System.currentTimeMillis();
            try {
                if (monitorDiskJob.isDiskIsFull()) {
                    log.error("硬盘剩余空间不足，gatway停止接收！！！！");
                    return;
                }
                rsp.setInt(Tag.Status, VR.US, 0);

                String cuid = rq.getString(Tag.AffectedSOPClassUID);
                String iuid = rq.getString(Tag.AffectedSOPInstanceUID);
                String tsuid = pc.getTransferSyntax();

                Attributes attributes = data.readDataset(tsuid);

                String StudyInstanceUID = attributes.getString(Tag.StudyInstanceUID);
                String SeriesInstanceUID = attributes.getString(Tag.SeriesInstanceUID);
                String SOPInstanceUID = attributes.getString(Tag.SOPInstanceUID);
                String AccessionNumber = attributes.getString(Tag.AccessionNumber);
                if (StringUtils.isEmpty(AccessionNumber)) {
                    AccessionNumber = "AccessionNumber";
                }

                String aetitle = as.getCallingAET();

                Path path = Paths.get(gateWayProperties.getStorage(),
                        aetitle, AccessionNumber, StudyInstanceUID, SeriesInstanceUID);
                File file = new File(path + "/" + SOPInstanceUID + ".dcm");
                try {
                    storeTo(as, as.createFileMetaInformation(iuid, cuid, tsuid), attributes, file);
                    studyTransferManager.onNewDicom(aetitle, StudyInstanceUID, AccessionNumber, SeriesInstanceUID, SOPInstanceUID);
                } catch (Exception e) {
                    deleteFile(as, file);
                    throw new DicomServiceException(Status.ProcessingFailure, e);
                }
            } finally {
                long to = System.currentTimeMillis();
                Metrics.timer("op_gateway_store").record(to - from, TimeUnit.MILLISECONDS);
            }
        }
    };

    private volatile String aet;

    private final AssociationHandler associationHandler = new AssociationHandler() {
        @Override
        protected AAssociateAC negotiate(Association as, AAssociateRQ rq)
                throws IOException {
            if (!rq.getCalledAET().equals(aet)) {
                throw new AAssociateRJ(AAssociateRJ.RESULT_REJECTED_PERMANENT,
                        AAssociateRJ.SOURCE_SERVICE_USER,
                        AAssociateRJ.REASON_CALLED_AET_NOT_RECOGNIZED);
            }
            return super.negotiate(as, rq);
        }
    };

    private final AssociationMonitor associationMonitor = new AssociationMonitor() {
        @Override
        public void onAssociationEstablished(Association as) {
        }

        @Override
        public void onAssociationFailed(Association as, Throwable e) {
        }

        @Override
        public void onAssociationRejected(Association as, AAssociateRJ aarj) {
            LOG.warn("Association rejected {}<-{} {}", as.getCalledAET(), as.getCallingAET(), aarj.getLocalizedMessage());
        }

        @Override
        public void onAssociationAccepted(Association as) {
            LOG.info("Association accepted {}<-{}", as.getCalledAET(), as.getCallingAET());
            as.addAssociationListener(association -> {
                LOG.info("Association closed {}<-{}", association.getCalledAET(), association.getCallingAET());
            });
        }
    };

    public void run() throws Exception {
        Device device = new Device(gateWayProperties.getName());
        device.setAssociationHandler(associationHandler);
        device.setAssociationMonitor(associationMonitor);

        final ApplicationEntity ae = new ApplicationEntity("*");
        Connection conn = new Connection();
        ae.addTransferCapability(
                new TransferCapability(null,
                        "*",
                        TransferCapability.Role.SCP,
                        "*"));
        device.setDimseRQHandler(createServiceRegistry());
        device.addConnection(conn);
        device.addApplicationEntity(ae);
        ae.setAssociationAcceptor(true);
        ae.addConnection(conn);

        conn.setHostname(gateWayProperties.getListenAddr());
        conn.setPort(gateWayProperties.getListenPort());

        CommandLineParser parser = new DetectEndOfOptionsPosixParser();
        String[] args = {};
        Options opts = new Options();
        CommandLine cl = parser.parse(opts, args);
        CLIUtils.configure(conn, cl);
        ExecutorService executorService = Executors.newCachedThreadPool();
        ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
        device.setScheduledExecutor(scheduledExecutorService);
        device.setExecutor(executorService);
        device.bindConnections();
    }

    protected void storeTo(Association as, Attributes fmi,
                           Attributes attributes, File file) throws IOException {
        file.getParentFile().mkdirs();
        DicomOutputStream out = new DicomOutputStream(file);
        try {
            out.writeDataset(fmi, attributes);
        } finally {
            SafeClose.close(out);
        }
    }

    private void deleteFile(Association as, File file) {
        if (file.delete())
            LOG.debug("{}: M-DELETE {}", as, file);
        else
            LOG.warn("{}: M-DELETE {} failed!", as, file);
    }

    private DicomServiceRegistry createServiceRegistry() {
        DicomServiceRegistry serviceRegistry = new DicomServiceRegistry();
        serviceRegistry.addDicomService(new BasicCEchoSCP());
        serviceRegistry.addDicomService(cstoreSCP);
        return serviceRegistry;
    }

    public void setAETitle(String aet) {
        this.aet = aet;
    }
}
